diff options
| author | Fuwn <[email protected]> | 2024-10-09 00:41:20 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2024-10-09 00:41:43 -0700 |
| commit | 998b63a35256ac985a5a2714dd1ca451af4dfd8a (patch) | |
| tree | 50796121a9d5ab0330fdc5d7e098bda2860d9726 /src/routes/user/[user] | |
| parent | feat(graphql): add badgeCount field (diff) | |
| download | due.moe-998b63a35256ac985a5a2714dd1ca451af4dfd8a.tar.xz due.moe-998b63a35256ac985a5a2714dd1ca451af4dfd8a.zip | |
chore(prettier): use spaces instead of tabs
Diffstat (limited to 'src/routes/user/[user]')
| -rw-r--r-- | src/routes/user/[user]/+page.gql | 30 | ||||
| -rw-r--r-- | src/routes/user/[user]/+page.svelte | 1076 | ||||
| -rw-r--r-- | src/routes/user/[user]/+page.ts | 24 | ||||
| -rw-r--r-- | src/routes/user/[user]/badges/+page.gql | 54 | ||||
| -rw-r--r-- | src/routes/user/[user]/badges/+page.svelte | 2040 |
5 files changed, 1612 insertions, 1612 deletions
diff --git a/src/routes/user/[user]/+page.gql b/src/routes/user/[user]/+page.gql index fd31248b..491290aa 100644 --- a/src/routes/user/[user]/+page.gql +++ b/src/routes/user/[user]/+page.gql @@ -1,18 +1,18 @@ query Profile($id: Int!) { - User(id: $id) { - id - badgesCount + User(id: $id) { + id + badgesCount - preferences { - created_at - updated_at - user_id - pinned_hololive_streams - hide_missing_badges - biography - badge_wall_css - hide_awc_badges - pinned_badge_wall_categories - } - } + preferences { + created_at + updated_at + user_id + pinned_hololive_streams + hide_missing_badges + biography + badge_wall_css + hide_awc_badges + pinned_badge_wall_categories + } + } } diff --git a/src/routes/user/[user]/+page.svelte b/src/routes/user/[user]/+page.svelte index 84f121ea..d60ea8e5 100644 --- a/src/routes/user/[user]/+page.svelte +++ b/src/routes/user/[user]/+page.svelte @@ -1,550 +1,550 @@ <script lang="ts"> - import settings from '$stores/settings'; - import ParallaxImage from '../../../lib/Image/ParallaxImage.svelte'; - import { typeSchedule, type ParseResult } from '$lib/Hololive/hololive'; - import HeadTitle from '$lib/Home/HeadTitle.svelte'; - import Message from '$lib/Loading/Message.svelte'; - import { estimatedDayReading } from '$lib/Media/Manga/time'; - import Skeleton from '$lib/Loading/Skeleton.svelte'; - import root from '$lib/Utility/root'; - import locale from '$stores/locale'; - import { onMount } from 'svelte'; - import authorisedUsers from '$lib/Data/Static/authorised.json'; - import tooltip from '$lib/Tooltip/tooltip'; - import AnimeRateLimited from '$lib/Error/AnimeRateLimited.svelte'; - import identity from '$stores/identity'; - import SettingHint from '$lib/Settings/SettingHint.svelte'; - import proxy from '$lib/Utility/proxy'; - import { parseScheduleHtml } from '$lib/Data/hololive'; - import type { Preferences } from '../../../graphql/$types'; - import SvelteMarkdown from 'svelte-markdown'; - import MarkdownLink from '$lib/MarkdownLink.svelte'; - import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte'; - import { graphql } from '$houdini'; - - export let data; - - $: ({ Profile } = data); - $: preferences = $Profile.fetching ? undefined : ($Profile.data?.User.preferences as Preferences); - - const setCategoriesQuery = graphql(` - mutation SetCategories($categories: [String!]!) { - setPinnedBadgeWallCategories(categories: $categories) { - id - - preferences { - pinned_badge_wall_categories - } - } - } - `); - - const toggleCategoryQuery = graphql(` - mutation ToggleCategory($category: String!) { - togglePinnedBadgeWallCategory(category: $category) { - id - - preferences { - pinned_badge_wall_categories - } - } - } - `); - - const toggleHideMissingBadgesQuery = graphql(` - mutation ToggleHideMissingBadges { - toggleHideMissingBadges { - id - - preferences { - hide_missing_badges - } - } - } - `); - - const toggleHideAWCBadgesQuery = graphql(` - mutation ToggleHideAWCBadges { - toggleHideAWCBadges { - id - - preferences { - hide_awc_badges - } - } - } - `); - - const setBiographyQuery = graphql(` - mutation SetBiography($biography: String!) { - setBiography(biography: $biography) { - id - - preferences { - biography - } - } - } - `); - - const setBadgeWallCSSQuery = graphql(` - mutation SetBadgeWallCSS($css: String!) { - setBadgeWallCSS(css: $css) { - id - - preferences { - badge_wall_css - } - } - } - `); - - $: userData = data.userData; - - let error = false; - let schedule: ParseResult | undefined = undefined; - let draggedCategory: string | null = null; - let draggedOverCategory: string | null = null; - - $: displayBadges = (username: string, badges: number | string) => - $locale({ - values: { - badges: badges, - username - } - }).user.profile.badges; - - const handleDragStart = ( - event: DragEvent & { currentTarget: EventTarget & HTMLDivElement }, - category: string | null - ) => { - draggedCategory = category; - - if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move'; - }; - - const handleDragOver = (event: any) => { - event.preventDefault(); - - event.dataTransfer.dropEffect = 'move'; - }; - - const handleDragEnter = ( - event: DragEvent & { currentTarget: EventTarget & HTMLDivElement }, - category: string | null - ) => { - event.preventDefault(); - - if (draggedCategory !== category && preferences && draggedCategory) { - draggedOverCategory = category; - - const categories = preferences.pinned_badge_wall_categories; - const draggedIndex = categories.indexOf(draggedCategory); - const targetIndex = categories.indexOf(category || ''); - - categories.splice(draggedIndex, 1); - categories.splice(targetIndex, 0, draggedCategory); - - preferences.pinned_badge_wall_categories = categories; - } - }; - - const handleDragLeave = ( - event: DragEvent & { currentTarget: EventTarget & HTMLDivElement }, - category: string - ) => { - event.preventDefault(); - - if (draggedOverCategory === category && preferences && draggedCategory) { - draggedOverCategory = null; - - const categories = preferences.pinned_badge_wall_categories; - const draggedIndex = categories.indexOf(draggedCategory); - - categories.splice(draggedIndex, 1); - categories.splice(categories.indexOf(category) + 1, 0, draggedCategory); - - preferences.pinned_badge_wall_categories = categories; - } - }; - - const handleDrop = (event: { preventDefault: () => void }) => { - event.preventDefault(); - - if (userData && preferences) - setCategoriesQuery - .mutate({ - categories: preferences.pinned_badge_wall_categories - }) - .then(); - - draggedCategory = null; - draggedOverCategory = null; - }; - - onMount(async () => { - schedule = typeSchedule( - parseScheduleHtml( - await ( - await fetch(proxy('https://schedule.hololive.tv'), { - headers: { - Cookie: 'timezone=Asia/Tokyo' - } - }) - ).text() - ) - ); - }); - - const getBadgeWallCSS = () => - (document.getElementById('badgeWallCSS') as HTMLTextAreaElement).value; - - const getBiography = () => - (document.getElementById('biography') as HTMLTextAreaElement).value.slice(0, 3000); - - const toggleCategory = () => { - if (!userData) return; - - const categoryElement = document.getElementById('category') as HTMLInputElement; - const category = categoryElement.value; - - toggleCategoryQuery.mutate({ category }).then(); - - categoryElement.value = ''; - }; + import settings from '$stores/settings'; + import ParallaxImage from '../../../lib/Image/ParallaxImage.svelte'; + import { typeSchedule, type ParseResult } from '$lib/Hololive/hololive'; + import HeadTitle from '$lib/Home/HeadTitle.svelte'; + import Message from '$lib/Loading/Message.svelte'; + import { estimatedDayReading } from '$lib/Media/Manga/time'; + import Skeleton from '$lib/Loading/Skeleton.svelte'; + import root from '$lib/Utility/root'; + import locale from '$stores/locale'; + import { onMount } from 'svelte'; + import authorisedUsers from '$lib/Data/Static/authorised.json'; + import tooltip from '$lib/Tooltip/tooltip'; + import AnimeRateLimited from '$lib/Error/AnimeRateLimited.svelte'; + import identity from '$stores/identity'; + import SettingHint from '$lib/Settings/SettingHint.svelte'; + import proxy from '$lib/Utility/proxy'; + import { parseScheduleHtml } from '$lib/Data/hololive'; + import type { Preferences } from '../../../graphql/$types'; + import SvelteMarkdown from 'svelte-markdown'; + import MarkdownLink from '$lib/MarkdownLink.svelte'; + import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte'; + import { graphql } from '$houdini'; + + export let data; + + $: ({ Profile } = data); + $: preferences = $Profile.fetching ? undefined : ($Profile.data?.User.preferences as Preferences); + + const setCategoriesQuery = graphql(` + mutation SetCategories($categories: [String!]!) { + setPinnedBadgeWallCategories(categories: $categories) { + id + + preferences { + pinned_badge_wall_categories + } + } + } + `); + + const toggleCategoryQuery = graphql(` + mutation ToggleCategory($category: String!) { + togglePinnedBadgeWallCategory(category: $category) { + id + + preferences { + pinned_badge_wall_categories + } + } + } + `); + + const toggleHideMissingBadgesQuery = graphql(` + mutation ToggleHideMissingBadges { + toggleHideMissingBadges { + id + + preferences { + hide_missing_badges + } + } + } + `); + + const toggleHideAWCBadgesQuery = graphql(` + mutation ToggleHideAWCBadges { + toggleHideAWCBadges { + id + + preferences { + hide_awc_badges + } + } + } + `); + + const setBiographyQuery = graphql(` + mutation SetBiography($biography: String!) { + setBiography(biography: $biography) { + id + + preferences { + biography + } + } + } + `); + + const setBadgeWallCSSQuery = graphql(` + mutation SetBadgeWallCSS($css: String!) { + setBadgeWallCSS(css: $css) { + id + + preferences { + badge_wall_css + } + } + } + `); + + $: userData = data.userData; + + let error = false; + let schedule: ParseResult | undefined = undefined; + let draggedCategory: string | null = null; + let draggedOverCategory: string | null = null; + + $: displayBadges = (username: string, badges: number | string) => + $locale({ + values: { + badges: badges, + username + } + }).user.profile.badges; + + const handleDragStart = ( + event: DragEvent & { currentTarget: EventTarget & HTMLDivElement }, + category: string | null + ) => { + draggedCategory = category; + + if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move'; + }; + + const handleDragOver = (event: any) => { + event.preventDefault(); + + event.dataTransfer.dropEffect = 'move'; + }; + + const handleDragEnter = ( + event: DragEvent & { currentTarget: EventTarget & HTMLDivElement }, + category: string | null + ) => { + event.preventDefault(); + + if (draggedCategory !== category && preferences && draggedCategory) { + draggedOverCategory = category; + + const categories = preferences.pinned_badge_wall_categories; + const draggedIndex = categories.indexOf(draggedCategory); + const targetIndex = categories.indexOf(category || ''); + + categories.splice(draggedIndex, 1); + categories.splice(targetIndex, 0, draggedCategory); + + preferences.pinned_badge_wall_categories = categories; + } + }; + + const handleDragLeave = ( + event: DragEvent & { currentTarget: EventTarget & HTMLDivElement }, + category: string + ) => { + event.preventDefault(); + + if (draggedOverCategory === category && preferences && draggedCategory) { + draggedOverCategory = null; + + const categories = preferences.pinned_badge_wall_categories; + const draggedIndex = categories.indexOf(draggedCategory); + + categories.splice(draggedIndex, 1); + categories.splice(categories.indexOf(category) + 1, 0, draggedCategory); + + preferences.pinned_badge_wall_categories = categories; + } + }; + + const handleDrop = (event: { preventDefault: () => void }) => { + event.preventDefault(); + + if (userData && preferences) + setCategoriesQuery + .mutate({ + categories: preferences.pinned_badge_wall_categories + }) + .then(); + + draggedCategory = null; + draggedOverCategory = null; + }; + + onMount(async () => { + schedule = typeSchedule( + parseScheduleHtml( + await ( + await fetch(proxy('https://schedule.hololive.tv'), { + headers: { + Cookie: 'timezone=Asia/Tokyo' + } + }) + ).text() + ) + ); + }); + + const getBadgeWallCSS = () => + (document.getElementById('badgeWallCSS') as HTMLTextAreaElement).value; + + const getBiography = () => + (document.getElementById('biography') as HTMLTextAreaElement).value.slice(0, 3000); + + const toggleCategory = () => { + if (!userData) return; + + const categoryElement = document.getElementById('category') as HTMLInputElement; + const category = categoryElement.value; + + toggleCategoryQuery.mutate({ category }).then(); + + categoryElement.value = ''; + }; - // 8.5827814569536423841e0 + // 8.5827814569536423841e0 </script> <HeadTitle route={`${data.username}'s Profile`} path={`/user/${data.username}`} /> {#if error} - <AnimeRateLimited> - <a href={`https://anilist.co/user/${data.username}`} target="_blank">@{data.username}</a>'s - profile could not be loaded. - </AnimeRateLimited> + <AnimeRateLimited> + <a href={`https://anilist.co/user/${data.username}`} target="_blank">@{data.username}</a>'s + profile could not be loaded. + </AnimeRateLimited> {:else} - {#if userData === null} - <Message slot withReload> - <p> - Could not load user profile for <a - href={`https://anilist.co/user/${data.username}`} - target="_blank">@{data.username}</a - >. - </p> - </Message> - {:else if userData === undefined} - <Skeleton card={false} bigCard count={1} height="224px" /> - - <Message message="Loading user profile ..." /> - {:else} - <div class="card card-small"> - <div - class="card user-grid" - style={`background-image: ${ - userData ? `url(${userData.bannerImage})` : 'none' - }; padding: 0;`} - > - {#if userData} - <img src={userData.bannerImage} alt="" class="cover-image" /> - {/if} - - <div class="card user-grid-content"> - <div class="user-grid-avatar"> - <LinkedTooltip content={`${userData.name}`} id="avatar" pin="avatar" relative> - <a href={`https://anilist.co/user/${userData.name}`} target="_blank"> - <ParallaxImage - source={$settings.displayDataSaver - ? userData.avatar.medium - : userData.avatar.large} - alternativeText="" - style="border-radius: 8px; width: 6.5em;" - /> - </a> - </LinkedTooltip> - </div> - - <div class="user-grid-rest"> - <p> - <a - href={`https://anilist.co/user/${userData.name}`} - target="_blank" - title={String(userData.id)} - use:tooltip - > - @{userData.name} - </a> - {#if userData && authorisedUsers.includes(userData.id)} - ‌ - <button class="unclickable-button button-badge badge-rainbow">Owner</button> - {/if} - <span class="click-item separator opaque">•</span> - <a href={root(`/user/${userData.name}/badges`)}>Badge Wall</a> - </p> - - {#if preferences && preferences.biography && preferences.biography.length > 0} - <SvelteMarkdown - source={preferences.biography} - renderers={{ - link: MarkdownLink - }} - /> - {/if} - - {$locale({ - values: { - username: userData.name, - anime: (userData.statistics.anime.minutesWatched / 60 / 24).toFixed(1), - manga: estimatedDayReading(userData.statistics.manga.chaptersRead).toFixed(1) - } - }).user.profile.statistics} - - {#if schedule && preferences && preferences.biography && preferences.biography.length > 0} - <br /> - {:else} - <p /> - {/if} - - {#if $Profile.fetching} - {displayBadges(userData.name, '...')} - {:else if $Profile.data && $Profile.data.User} - {@const badges = $Profile.data.User.badgesCount} - - {#if badges} - {displayBadges(userData.name, badges)} - {:else} - {displayBadges(userData.name, '?')} - {/if} - {:else} - {displayBadges(userData.name, '?')} - {/if} - </div> - </div> - </div> - </div> - {/if} - - {#if schedule && preferences && preferences.pinned_hololive_streams.length > 0} - <p /> - - <div class="card"> - <div class="hololive-badges"> - {#each preferences.pinned_hololive_streams as stream, index} - {@const avatar = schedule.dict[stream]} - - {#if avatar} - <LinkedTooltip - content={stream} - id={`hololive-badge-${index}`} - pin={`hololive-badge-${index}`} - relative - > - <a href={root(`/hololive/${encodeURIComponent(stream)}`)}> - <div class="user-grid-hololive-badges"> - <ParallaxImage source={avatar} alternativeText="Avatar" /> - </div> - </a> - </LinkedTooltip> - {/if} - {/each} - </div> - </div> - {/if} - - {#if preferences && userData && userData.id === $identity.id} - <p /> - - <details open> - <summary>{$locale().user.preferences.title}</summary> - - <input - type="checkbox" - on:change={() => { - if (userData) toggleHideMissingBadgesQuery.mutate(null).then(); - }} - checked={preferences.hide_missing_badges} - /> - {$locale().user.preferences.hideMissingBadges.title} - <SettingHint lineBreak>{$locale().user.preferences.hideMissingBadges.hint}</SettingHint> - - <p /> - - <input - type="checkbox" - on:change={() => { - if (userData) toggleHideAWCBadgesQuery.mutate(null).then(); - }} - checked={preferences.hide_awc_badges} - /> - {$locale().user.preferences.hideAWCBadges.title} - - <p /> - - Pinned Categories - - <div class="pinned-categories"> - {#each preferences.pinned_badge_wall_categories as category} - <div - class="card card-small pinned-category" - draggable="true" - on:dragstart={(event) => handleDragStart(event, category)} - on:dragover={handleDragOver} - on:dragenter={(event) => handleDragEnter(event, category)} - on:dragleave={(event) => handleDragLeave(event, category)} - on:drop={handleDrop} - role="button" - tabindex="0" - > - <span class="pinned-category-name"> - {category} - </span> - - <button - on:click={() => { - if (userData) toggleCategoryQuery.mutate({ category }).then(); - }}>Remove</button - > - </div> - {/each} - - <span class="card card-small pinned-category"> - <span class="pinned-category-name"> - <input type="text" id="category" placeholder="Category" style="width: 10em;" /> - </span> - - <button class="button-lined" on:click={toggleCategory}>Add</button> - </span> - </div> - - <p /> - - Biography - - <button - on:click={() => { - if (userData) - setBiographyQuery - .mutate({ - biography: getBiography() - }) - .then(); - }}>Save</button - > - <textarea - value={preferences.biography} - rows="5" - cols="100" - id="biography" - placeholder="Markdown supported!" - /> - - <p /> - - Badge Wall Custom CSS - - <button - on:click={() => { - if (userData) - setBadgeWallCSSQuery - .mutate({ - css: getBadgeWallCSS() - }) - .then(); - }}>Save</button - > - <textarea - value={preferences.badge_wall_css} - rows="10" - cols="100" - id="badgeWallCSS" - placeholder="/* Use classes and IDs such as .badges, #badges, .badge, or standard elements like body and details, or anything, as long as it's valid CSS! */" - /> - </details> - {/if} + {#if userData === null} + <Message slot withReload> + <p> + Could not load user profile for <a + href={`https://anilist.co/user/${data.username}`} + target="_blank">@{data.username}</a + >. + </p> + </Message> + {:else if userData === undefined} + <Skeleton card={false} bigCard count={1} height="224px" /> + + <Message message="Loading user profile ..." /> + {:else} + <div class="card card-small"> + <div + class="card user-grid" + style={`background-image: ${ + userData ? `url(${userData.bannerImage})` : 'none' + }; padding: 0;`} + > + {#if userData} + <img src={userData.bannerImage} alt="" class="cover-image" /> + {/if} + + <div class="card user-grid-content"> + <div class="user-grid-avatar"> + <LinkedTooltip content={`${userData.name}`} id="avatar" pin="avatar" relative> + <a href={`https://anilist.co/user/${userData.name}`} target="_blank"> + <ParallaxImage + source={$settings.displayDataSaver + ? userData.avatar.medium + : userData.avatar.large} + alternativeText="" + style="border-radius: 8px; width: 6.5em;" + /> + </a> + </LinkedTooltip> + </div> + + <div class="user-grid-rest"> + <p> + <a + href={`https://anilist.co/user/${userData.name}`} + target="_blank" + title={String(userData.id)} + use:tooltip + > + @{userData.name} + </a> + {#if userData && authorisedUsers.includes(userData.id)} + ‌ + <button class="unclickable-button button-badge badge-rainbow">Owner</button> + {/if} + <span class="click-item separator opaque">•</span> + <a href={root(`/user/${userData.name}/badges`)}>Badge Wall</a> + </p> + + {#if preferences && preferences.biography && preferences.biography.length > 0} + <SvelteMarkdown + source={preferences.biography} + renderers={{ + link: MarkdownLink + }} + /> + {/if} + + {$locale({ + values: { + username: userData.name, + anime: (userData.statistics.anime.minutesWatched / 60 / 24).toFixed(1), + manga: estimatedDayReading(userData.statistics.manga.chaptersRead).toFixed(1) + } + }).user.profile.statistics} + + {#if schedule && preferences && preferences.biography && preferences.biography.length > 0} + <br /> + {:else} + <p /> + {/if} + + {#if $Profile.fetching} + {displayBadges(userData.name, '...')} + {:else if $Profile.data && $Profile.data.User} + {@const badges = $Profile.data.User.badgesCount} + + {#if badges} + {displayBadges(userData.name, badges)} + {:else} + {displayBadges(userData.name, '?')} + {/if} + {:else} + {displayBadges(userData.name, '?')} + {/if} + </div> + </div> + </div> + </div> + {/if} + + {#if schedule && preferences && preferences.pinned_hololive_streams.length > 0} + <p /> + + <div class="card"> + <div class="hololive-badges"> + {#each preferences.pinned_hololive_streams as stream, index} + {@const avatar = schedule.dict[stream]} + + {#if avatar} + <LinkedTooltip + content={stream} + id={`hololive-badge-${index}`} + pin={`hololive-badge-${index}`} + relative + > + <a href={root(`/hololive/${encodeURIComponent(stream)}`)}> + <div class="user-grid-hololive-badges"> + <ParallaxImage source={avatar} alternativeText="Avatar" /> + </div> + </a> + </LinkedTooltip> + {/if} + {/each} + </div> + </div> + {/if} + + {#if preferences && userData && userData.id === $identity.id} + <p /> + + <details open> + <summary>{$locale().user.preferences.title}</summary> + + <input + type="checkbox" + on:change={() => { + if (userData) toggleHideMissingBadgesQuery.mutate(null).then(); + }} + checked={preferences.hide_missing_badges} + /> + {$locale().user.preferences.hideMissingBadges.title} + <SettingHint lineBreak>{$locale().user.preferences.hideMissingBadges.hint}</SettingHint> + + <p /> + + <input + type="checkbox" + on:change={() => { + if (userData) toggleHideAWCBadgesQuery.mutate(null).then(); + }} + checked={preferences.hide_awc_badges} + /> + {$locale().user.preferences.hideAWCBadges.title} + + <p /> + + Pinned Categories + + <div class="pinned-categories"> + {#each preferences.pinned_badge_wall_categories as category} + <div + class="card card-small pinned-category" + draggable="true" + on:dragstart={(event) => handleDragStart(event, category)} + on:dragover={handleDragOver} + on:dragenter={(event) => handleDragEnter(event, category)} + on:dragleave={(event) => handleDragLeave(event, category)} + on:drop={handleDrop} + role="button" + tabindex="0" + > + <span class="pinned-category-name"> + {category} + </span> + + <button + on:click={() => { + if (userData) toggleCategoryQuery.mutate({ category }).then(); + }}>Remove</button + > + </div> + {/each} + + <span class="card card-small pinned-category"> + <span class="pinned-category-name"> + <input type="text" id="category" placeholder="Category" style="width: 10em;" /> + </span> + + <button class="button-lined" on:click={toggleCategory}>Add</button> + </span> + </div> + + <p /> + + Biography + + <button + on:click={() => { + if (userData) + setBiographyQuery + .mutate({ + biography: getBiography() + }) + .then(); + }}>Save</button + > + <textarea + value={preferences.biography} + rows="5" + cols="100" + id="biography" + placeholder="Markdown supported!" + /> + + <p /> + + Badge Wall Custom CSS + + <button + on:click={() => { + if (userData) + setBadgeWallCSSQuery + .mutate({ + css: getBadgeWallCSS() + }) + .then(); + }}>Save</button + > + <textarea + value={preferences.badge_wall_css} + rows="10" + cols="100" + id="badgeWallCSS" + placeholder="/* Use classes and IDs such as .badges, #badges, .badge, or standard elements like body and details, or anything, as long as it's valid CSS! */" + /> + </details> + {/if} {/if} <style lang="scss"> - .user-grid-content { - display: flex; - flex-wrap: wrap; - column-gap: 1.5em; - background-color: rgba(0, 0, 0, 0.468); - color: #d8d8d8; - border-top-left-radius: 0; - border-top-right-radius: 0; - } - - .user-grid-avatar { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - } - - .click-item { - margin: 0 0.625rem; - } - - .user-grid { - background-size: cover; - background-position: center; - background-repeat: no-repeat; - } - - .cover-image { - visibility: hidden; - height: 4.5em; - } - - .user-grid-hololive-badges { - $transitionDuration: 0.45s; - $transition: transform $transitionDuration ease, box-shadow $transitionDuration ease; - $size: 5.25em; - - border-radius: 8px; - width: $size; - height: $size; - object-fit: cover; - transition: $transition; - box-shadow: rgba(0, 0, 11, 0.1) 0px 7px 29px 0px; - overflow: hidden; - - &:hover { - z-index: 2; - transition: $transition; - transform: scale(1.15); - box-shadow: 0 1.5px 9px var(--base01), 0 0 0 4px var(--base02), 0 4px 30px var(--base01); - } - } - - .hololive-badges { - display: flex; - gap: 1rem; - border-radius: 8px; - justify-content: space-around; - flex-wrap: wrap; - } - - .separator { - color: var(--base04); - } - - .user-grid-rest { - flex: 1; - } - - .pinned-categories { - display: flex; - flex-wrap: wrap; - gap: 1rem; - } - - .pinned-category { - display: flex; - align-items: center; - } - - .pinned-category-name { - margin-right: 0.5em; - } + .user-grid-content { + display: flex; + flex-wrap: wrap; + column-gap: 1.5em; + background-color: rgba(0, 0, 0, 0.468); + color: #d8d8d8; + border-top-left-radius: 0; + border-top-right-radius: 0; + } + + .user-grid-avatar { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + + .click-item { + margin: 0 0.625rem; + } + + .user-grid { + background-size: cover; + background-position: center; + background-repeat: no-repeat; + } + + .cover-image { + visibility: hidden; + height: 4.5em; + } + + .user-grid-hololive-badges { + $transitionDuration: 0.45s; + $transition: transform $transitionDuration ease, box-shadow $transitionDuration ease; + $size: 5.25em; + + border-radius: 8px; + width: $size; + height: $size; + object-fit: cover; + transition: $transition; + box-shadow: rgba(0, 0, 11, 0.1) 0px 7px 29px 0px; + overflow: hidden; + + &:hover { + z-index: 2; + transition: $transition; + transform: scale(1.15); + box-shadow: 0 1.5px 9px var(--base01), 0 0 0 4px var(--base02), 0 4px 30px var(--base01); + } + } + + .hololive-badges { + display: flex; + gap: 1rem; + border-radius: 8px; + justify-content: space-around; + flex-wrap: wrap; + } + + .separator { + color: var(--base04); + } + + .user-grid-rest { + flex: 1; + } + + .pinned-categories { + display: flex; + flex-wrap: wrap; + gap: 1rem; + } + + .pinned-category { + display: flex; + align-items: center; + } + + .pinned-category-name { + margin-right: 0.5em; + } </style> diff --git a/src/routes/user/[user]/+page.ts b/src/routes/user/[user]/+page.ts index c84caafb..e44f06aa 100644 --- a/src/routes/user/[user]/+page.ts +++ b/src/routes/user/[user]/+page.ts @@ -3,17 +3,17 @@ import { user } from '$lib/Data/AniList/user'; import type { LoadEvent } from '@sveltejs/kit'; export const load = async (event: LoadEvent) => { - const username = event.params.user as string; - const userData = await user(username, /^\d+$/.test(username)); + const username = event.params.user as string; + const userData = await user(username, /^\d+$/.test(username)); - return { - ...(await load_Profile({ - event, - variables: { - id: userData.id - } - })), - username, - userData - }; + return { + ...(await load_Profile({ + event, + variables: { + id: userData.id + } + })), + username, + userData + }; }; diff --git a/src/routes/user/[user]/badges/+page.gql b/src/routes/user/[user]/badges/+page.gql index afdd797d..060b38e7 100644 --- a/src/routes/user/[user]/badges/+page.gql +++ b/src/routes/user/[user]/badges/+page.gql @@ -1,31 +1,31 @@ query BadgeWallUser($id: Int!) { - User(id: $id) { - id + User(id: $id) { + id - badges { - post - image - description - id - time - category - hidden - source - designer - shadow_hidden - click_count - } + badges { + post + image + description + id + time + category + hidden + source + designer + shadow_hidden + click_count + } - preferences { - created_at - updated_at - user_id - pinned_hololive_streams - hide_missing_badges - biography - badge_wall_css - hide_awc_badges - pinned_badge_wall_categories - } - } + preferences { + created_at + updated_at + user_id + pinned_hololive_streams + hide_missing_badges + biography + badge_wall_css + hide_awc_badges + pinned_badge_wall_categories + } + } } diff --git a/src/routes/user/[user]/badges/+page.svelte b/src/routes/user/[user]/badges/+page.svelte index 3857d18b..44dd852a 100644 --- a/src/routes/user/[user]/badges/+page.svelte +++ b/src/routes/user/[user]/badges/+page.svelte @@ -1,1042 +1,1042 @@ <script lang="ts"> - import AWC from './../../../../lib/User/BadgeWall/AWC.svelte'; - import { user, type User } from '$lib/Data/AniList/user'; - import type { Badge } from '../../../../graphql/$types'; - import { onDestroy, onMount } from 'svelte'; - import HeadTitle from '$lib/Home/HeadTitle.svelte'; - import { databaseTimeToDate, dateToInputTime, inputTimeToDatabaseTime } from '$lib/Utility/time'; - import proxy from '$lib/Utility/proxy'; - import locale from '$stores/locale'; - import Skeleton from '$lib/Loading/Skeleton.svelte'; - import Message from '$lib/Loading/Message.svelte'; - import Dropdown from '$lib/Layout/Dropdown.svelte'; - import { activityText } from '$lib/Data/AniList/activity'; - import SettingHint from '$lib/Settings/SettingHint.svelte'; - import Popup from '$lib/Layout/Popup.svelte'; - import { page } from '$app/stores'; - import { browser } from '$app/environment'; - import BadgePreview from '$lib/User/BadgeWall/BadgePreview.svelte'; - import authorisedJson from '$lib/Data/Static/authorised.json'; - import identity from '$stores/identity'; - import '$lib/User/BadgeWall/badges.css'; - import Badges from '$lib/User/BadgeWall/Badges.svelte'; - import type { IndexedBadge } from '$lib/User/BadgeWall/badge'; - import { graphql } from '$houdini'; - import type { Preferences } from '../../../../graphql/user/$types'; - - export let data; - - $: ({ BadgeWallUser } = data); - $: preferences = $BadgeWallUser.fetching - ? undefined - : ($BadgeWallUser.data?.User.preferences as Preferences); - - $: if (browser && preferences && preferences.badge_wall_css) { - const sanitise = (css: string) => - css - .replace(/\/\*[\s\S]*?\*\//g, '') - .replace(/<\/?[^>]+(>|$)/g, '') - .replace( - /(expression|javascript|vbscript|onerror|onload|onclick|onmouseover|onmouseout|onmouseup|onmousedown|onkeydown|onkeyup|onkeypress|onblur|onfocus|onsubmit|onreset|onselect|onchange|ondblclick):/gi, - '' - ) - .replace(/(behaviour|behavior|moz-binding|content):/gi, '') - .replace(/\s+/g, ' ') - .trim(); - const style = document.createElement('style'); - - style.dataset.badgeWall = 'true'; - style.innerHTML = sanitise(preferences.badge_wall_css); - - document.head.appendChild(style); - } - - const updateBadgeQuery = graphql(` - mutation UpdateBadge( - $id: Int - $post: String - $image: String - $description: String - $time: String - $category: String - $hidden: Boolean - $source: String - $designer: String - ) { - updateBadge( - id: $id - post: $post - image: $image - description: $description - time: $time - category: $category - hidden: $hidden - source: $source - designer: $designer - ) { - id - - badges { - post - image - description - id - time - category - hidden - source - designer - shadow_hidden - click_count - } - } - } - `); - - const pruneBadgesQuery = graphql(` - mutation PruneUserBadges { - pruneUserBadges { - id - - badges { - post - image - description - id - time - category - hidden - source - designer - shadow_hidden - click_count - } - } - } - `); - - const hideCategoryQuery = graphql(` - mutation HideCategory($category: String) { - hideBadge(category: $category) { - id - - badges { - post - image - description - id - time - category - hidden - source - designer - shadow_hidden - click_count - } - } - } - `); - - const deleteBadgeQuery = graphql(` - mutation DeleteBadge($id: Int!) { - deleteBadge(id: $id) { - id - - badges { - post - image - description - id - time - category - hidden - source - designer - shadow_hidden - click_count - } - } - } - `); - - const shadowHideBadgeQuery = graphql(` - mutation ShadowHideBadge($id: Int!, $state: Boolean) { - shadowHideBadge(id: $id, state: $state) { - id - - badges { - id - } - } - } - `); - - interface ImportImage { - link?: string; - image: string; - } - - let editMode = false; - let importMode = false; - let error: null | string; - let awcPromise: Promise<Response>; - let confirmDelete = 0; - let confirmPrune = 0; - let selectedBadge: IndexedBadge | undefined = undefined; - let loadError: string | null = null; - const isId = /^\d+$/.test(data.username); - let importImages: ImportImage[] | undefined = undefined; - let importLinks = false; - let importCategory = ''; - let importReplies = false; - let badger: Partial<User>; - let migrateMode = false; - let hideMode = false; - const authorised = authorisedJson.includes($identity.id); - let noticeDismissed = false; - - $: categoryFilter = new URLSearchParams($page.url.searchParams).get('category'); - - type GroupedBadges = { [key: string]: IndexedBadge[] }; - - const setShadowHide = () => - shadowHideBadgeQuery.mutate({ - id: badger.id as number - }); - - onMount(async () => { - if (browser && localStorage.getItem('badgeWallNoticeDismissed')) noticeDismissed = true; - - badger = isId - ? { - id: parseInt(data.username), - name: 'User' - } - : await user(data.username); - - if (!isId && !badger) { - loadError = 'User not found.'; - - return; - } - - awcPromise = fetch(proxy(`https://awc.moe/challenger/${badger.name}`)); - }); - - onDestroy(() => { - if (browser) - Array.from(document.head.querySelectorAll('style')).forEach((style) => { - if (style.dataset.badgeWall) style.remove(); - }); - }); - - const submitBadge = () => { - const imageURL = document.querySelector('input[name="image_url"]') as HTMLInputElement; - const activityURL = document.querySelector('input[name="activity_url"]') as HTMLInputElement; - const description = document.querySelector('input[name="description"]') as HTMLInputElement; - const time = document.querySelector('input[type="datetime-local"]') as HTMLInputElement; - const category = document.querySelector('input[name="category"]') as HTMLInputElement; - const hidden = document.querySelector('input[name="hidden"]') as HTMLInputElement; - const source = document.querySelector('input[name="source"]') as HTMLInputElement; - const designer = document.querySelector('input[name="designer"]') as HTMLInputElement; - - if (!imageURL.value) { - error = 'Image URL cannot be empty.'; - - return; - } - - if ( - !imageURL.value.startsWith('http') || - (activityURL.value.length > 0 && !activityURL.value.startsWith('http')) - ) { - error = 'URLs must start with http or https.'; - - return; - } - - updateBadgeQuery - .mutate({ - id: selectedBadge?.id, - image: imageURL.value, - post: activityURL.value || '#', - description: description.value, - category: category.value, - time: time.valueAsDate ? inputTimeToDatabaseTime(time.valueAsDate) : undefined, - hidden: hidden.value === 'Hidden', - source: source.value, - designer: designer.value - }) - .then(() => { - error = null; - imageURL.value = ''; - activityURL.value = ''; - description.value = ''; - category.value = ''; - hidden.value = 'Shown'; - selectedBadge = undefined; - source.value = ''; - designer.value = ''; - }); - }; - - const removeAllBadges = () => { - if (confirmPrune === 2) { - confirmPrune = 0; - } else if (confirmPrune === 0) { - confirmPrune = 1; - - return; - } else { - confirmPrune = 2; - - return; - } - - selectedBadge = undefined; - - pruneBadgesQuery.mutate(null).then(); - }; - - const removeBadge = (badge: Badge) => { - if (!badge.id) return; - - if (confirmDelete === badge.id * 2) { - confirmDelete = 0; - } else if (confirmDelete / 4 === badge.id) { - confirmDelete = badge.id * 2; - - return; - } else { - confirmDelete = badge.id * 2; - - return; - } - - selectedBadge = undefined; - - deleteBadgeQuery - .mutate({ - id: badge.id - }) - .then(); - }; - - const groupBadges = (badges: IndexedBadge[]) => { - const groupedBadges: GroupedBadges = {}; - - badges.forEach((badge) => { - if (!badge.category) badge.category = 'Uncategorised'; - - if (!groupedBadges[badge.category]) groupedBadges[badge.category] = []; - - groupedBadges[badge.category].push(badge); - }); - - Object.entries(groupedBadges).forEach(([_category, badges]) => { - badges.forEach((badge, index) => { - badge.index = index; - }); - }); - - return Object.entries(groupedBadges) - .sort((a, b) => a[1].length - b[1].length) - .sort((a, b) => { - const pinnedCategories = - preferences && preferences.pinned_badge_wall_categories - ? preferences.pinned_badge_wall_categories - : ([] as string[]); - const aIndex = pinnedCategories.indexOf(a[0]); - const bIndex = pinnedCategories.indexOf(b[0]); - - if (aIndex === -1 && bIndex === -1) return 0; - if (aIndex === -1) return 1; - if (bIndex === -1) return -1; - - return aIndex - bIndex; - }) - .reduce((set: GroupedBadges, [key, value]) => { - set[key] = value; - - return set; - }, {}); - }; - - const parsePost = async () => { - if (importImages && importImages.length > 0) importImages = undefined; - - const link = (document.querySelector('#import_activity_url') as HTMLInputElement).value; - const type = link.replace(/.*\/(activity|thread)\/(\d+).*/, '$1'); - const id = link.replace(/.*\/(activity|thread)\/(\d+).*/, '$2'); - - if (type !== 'activity') return null; - - let text = await activityText(parseInt(id), importReplies); - - const images: ImportImage[] = []; - - if (importLinks) { - Array.from(new DOMParser().parseFromString(text, 'text/html').querySelectorAll('a')).forEach( - (a) => { - const anchor = a as HTMLAnchorElement; - - if (anchor.querySelector('img')) { - images.push({ - link: anchor.href, - image: (anchor.querySelector('img') as HTMLImageElement).src - }); - } - } - ); - - text = text.replace(/<a.*?>.*?<img.*?>.*?<\/a>/g, ''); - - Array.from( - new DOMParser().parseFromString(text, 'text/html').querySelectorAll('img') - ).forEach((img) => { - const image = img as HTMLImageElement; - - images.push({ - image: image.src - }); - }); - } else { - Array.from( - new DOMParser().parseFromString(text, 'text/html').querySelectorAll('img') - ).forEach((img) => { - const image = img as HTMLImageElement; - - images.push({ - image: image.src - }); - }); - } - - importImages = images; - }; - - const importBadges = () => - fetch( - `/api/badges?import=true + import AWC from './../../../../lib/User/BadgeWall/AWC.svelte'; + import { user, type User } from '$lib/Data/AniList/user'; + import type { Badge } from '../../../../graphql/$types'; + import { onDestroy, onMount } from 'svelte'; + import HeadTitle from '$lib/Home/HeadTitle.svelte'; + import { databaseTimeToDate, dateToInputTime, inputTimeToDatabaseTime } from '$lib/Utility/time'; + import proxy from '$lib/Utility/proxy'; + import locale from '$stores/locale'; + import Skeleton from '$lib/Loading/Skeleton.svelte'; + import Message from '$lib/Loading/Message.svelte'; + import Dropdown from '$lib/Layout/Dropdown.svelte'; + import { activityText } from '$lib/Data/AniList/activity'; + import SettingHint from '$lib/Settings/SettingHint.svelte'; + import Popup from '$lib/Layout/Popup.svelte'; + import { page } from '$app/stores'; + import { browser } from '$app/environment'; + import BadgePreview from '$lib/User/BadgeWall/BadgePreview.svelte'; + import authorisedJson from '$lib/Data/Static/authorised.json'; + import identity from '$stores/identity'; + import '$lib/User/BadgeWall/badges.css'; + import Badges from '$lib/User/BadgeWall/Badges.svelte'; + import type { IndexedBadge } from '$lib/User/BadgeWall/badge'; + import { graphql } from '$houdini'; + import type { Preferences } from '../../../../graphql/user/$types'; + + export let data; + + $: ({ BadgeWallUser } = data); + $: preferences = $BadgeWallUser.fetching + ? undefined + : ($BadgeWallUser.data?.User.preferences as Preferences); + + $: if (browser && preferences && preferences.badge_wall_css) { + const sanitise = (css: string) => + css + .replace(/\/\*[\s\S]*?\*\//g, '') + .replace(/<\/?[^>]+(>|$)/g, '') + .replace( + /(expression|javascript|vbscript|onerror|onload|onclick|onmouseover|onmouseout|onmouseup|onmousedown|onkeydown|onkeyup|onkeypress|onblur|onfocus|onsubmit|onreset|onselect|onchange|ondblclick):/gi, + '' + ) + .replace(/(behaviour|behavior|moz-binding|content):/gi, '') + .replace(/\s+/g, ' ') + .trim(); + const style = document.createElement('style'); + + style.dataset.badgeWall = 'true'; + style.innerHTML = sanitise(preferences.badge_wall_css); + + document.head.appendChild(style); + } + + const updateBadgeQuery = graphql(` + mutation UpdateBadge( + $id: Int + $post: String + $image: String + $description: String + $time: String + $category: String + $hidden: Boolean + $source: String + $designer: String + ) { + updateBadge( + id: $id + post: $post + image: $image + description: $description + time: $time + category: $category + hidden: $hidden + source: $source + designer: $designer + ) { + id + + badges { + post + image + description + id + time + category + hidden + source + designer + shadow_hidden + click_count + } + } + } + `); + + const pruneBadgesQuery = graphql(` + mutation PruneUserBadges { + pruneUserBadges { + id + + badges { + post + image + description + id + time + category + hidden + source + designer + shadow_hidden + click_count + } + } + } + `); + + const hideCategoryQuery = graphql(` + mutation HideCategory($category: String) { + hideBadge(category: $category) { + id + + badges { + post + image + description + id + time + category + hidden + source + designer + shadow_hidden + click_count + } + } + } + `); + + const deleteBadgeQuery = graphql(` + mutation DeleteBadge($id: Int!) { + deleteBadge(id: $id) { + id + + badges { + post + image + description + id + time + category + hidden + source + designer + shadow_hidden + click_count + } + } + } + `); + + const shadowHideBadgeQuery = graphql(` + mutation ShadowHideBadge($id: Int!, $state: Boolean) { + shadowHideBadge(id: $id, state: $state) { + id + + badges { + id + } + } + } + `); + + interface ImportImage { + link?: string; + image: string; + } + + let editMode = false; + let importMode = false; + let error: null | string; + let awcPromise: Promise<Response>; + let confirmDelete = 0; + let confirmPrune = 0; + let selectedBadge: IndexedBadge | undefined = undefined; + let loadError: string | null = null; + const isId = /^\d+$/.test(data.username); + let importImages: ImportImage[] | undefined = undefined; + let importLinks = false; + let importCategory = ''; + let importReplies = false; + let badger: Partial<User>; + let migrateMode = false; + let hideMode = false; + const authorised = authorisedJson.includes($identity.id); + let noticeDismissed = false; + + $: categoryFilter = new URLSearchParams($page.url.searchParams).get('category'); + + type GroupedBadges = { [key: string]: IndexedBadge[] }; + + const setShadowHide = () => + shadowHideBadgeQuery.mutate({ + id: badger.id as number + }); + + onMount(async () => { + if (browser && localStorage.getItem('badgeWallNoticeDismissed')) noticeDismissed = true; + + badger = isId + ? { + id: parseInt(data.username), + name: 'User' + } + : await user(data.username); + + if (!isId && !badger) { + loadError = 'User not found.'; + + return; + } + + awcPromise = fetch(proxy(`https://awc.moe/challenger/${badger.name}`)); + }); + + onDestroy(() => { + if (browser) + Array.from(document.head.querySelectorAll('style')).forEach((style) => { + if (style.dataset.badgeWall) style.remove(); + }); + }); + + const submitBadge = () => { + const imageURL = document.querySelector('input[name="image_url"]') as HTMLInputElement; + const activityURL = document.querySelector('input[name="activity_url"]') as HTMLInputElement; + const description = document.querySelector('input[name="description"]') as HTMLInputElement; + const time = document.querySelector('input[type="datetime-local"]') as HTMLInputElement; + const category = document.querySelector('input[name="category"]') as HTMLInputElement; + const hidden = document.querySelector('input[name="hidden"]') as HTMLInputElement; + const source = document.querySelector('input[name="source"]') as HTMLInputElement; + const designer = document.querySelector('input[name="designer"]') as HTMLInputElement; + + if (!imageURL.value) { + error = 'Image URL cannot be empty.'; + + return; + } + + if ( + !imageURL.value.startsWith('http') || + (activityURL.value.length > 0 && !activityURL.value.startsWith('http')) + ) { + error = 'URLs must start with http or https.'; + + return; + } + + updateBadgeQuery + .mutate({ + id: selectedBadge?.id, + image: imageURL.value, + post: activityURL.value || '#', + description: description.value, + category: category.value, + time: time.valueAsDate ? inputTimeToDatabaseTime(time.valueAsDate) : undefined, + hidden: hidden.value === 'Hidden', + source: source.value, + designer: designer.value + }) + .then(() => { + error = null; + imageURL.value = ''; + activityURL.value = ''; + description.value = ''; + category.value = ''; + hidden.value = 'Shown'; + selectedBadge = undefined; + source.value = ''; + designer.value = ''; + }); + }; + + const removeAllBadges = () => { + if (confirmPrune === 2) { + confirmPrune = 0; + } else if (confirmPrune === 0) { + confirmPrune = 1; + + return; + } else { + confirmPrune = 2; + + return; + } + + selectedBadge = undefined; + + pruneBadgesQuery.mutate(null).then(); + }; + + const removeBadge = (badge: Badge) => { + if (!badge.id) return; + + if (confirmDelete === badge.id * 2) { + confirmDelete = 0; + } else if (confirmDelete / 4 === badge.id) { + confirmDelete = badge.id * 2; + + return; + } else { + confirmDelete = badge.id * 2; + + return; + } + + selectedBadge = undefined; + + deleteBadgeQuery + .mutate({ + id: badge.id + }) + .then(); + }; + + const groupBadges = (badges: IndexedBadge[]) => { + const groupedBadges: GroupedBadges = {}; + + badges.forEach((badge) => { + if (!badge.category) badge.category = 'Uncategorised'; + + if (!groupedBadges[badge.category]) groupedBadges[badge.category] = []; + + groupedBadges[badge.category].push(badge); + }); + + Object.entries(groupedBadges).forEach(([_category, badges]) => { + badges.forEach((badge, index) => { + badge.index = index; + }); + }); + + return Object.entries(groupedBadges) + .sort((a, b) => a[1].length - b[1].length) + .sort((a, b) => { + const pinnedCategories = + preferences && preferences.pinned_badge_wall_categories + ? preferences.pinned_badge_wall_categories + : ([] as string[]); + const aIndex = pinnedCategories.indexOf(a[0]); + const bIndex = pinnedCategories.indexOf(b[0]); + + if (aIndex === -1 && bIndex === -1) return 0; + if (aIndex === -1) return 1; + if (bIndex === -1) return -1; + + return aIndex - bIndex; + }) + .reduce((set: GroupedBadges, [key, value]) => { + set[key] = value; + + return set; + }, {}); + }; + + const parsePost = async () => { + if (importImages && importImages.length > 0) importImages = undefined; + + const link = (document.querySelector('#import_activity_url') as HTMLInputElement).value; + const type = link.replace(/.*\/(activity|thread)\/(\d+).*/, '$1'); + const id = link.replace(/.*\/(activity|thread)\/(\d+).*/, '$2'); + + if (type !== 'activity') return null; + + let text = await activityText(parseInt(id), importReplies); + + const images: ImportImage[] = []; + + if (importLinks) { + Array.from(new DOMParser().parseFromString(text, 'text/html').querySelectorAll('a')).forEach( + (a) => { + const anchor = a as HTMLAnchorElement; + + if (anchor.querySelector('img')) { + images.push({ + link: anchor.href, + image: (anchor.querySelector('img') as HTMLImageElement).src + }); + } + } + ); + + text = text.replace(/<a.*?>.*?<img.*?>.*?<\/a>/g, ''); + + Array.from( + new DOMParser().parseFromString(text, 'text/html').querySelectorAll('img') + ).forEach((img) => { + const image = img as HTMLImageElement; + + images.push({ + image: image.src + }); + }); + } else { + Array.from( + new DOMParser().parseFromString(text, 'text/html').querySelectorAll('img') + ).forEach((img) => { + const image = img as HTMLImageElement; + + images.push({ + image: image.src + }); + }); + } + + importImages = images; + }; + + const importBadges = () => + fetch( + `/api/badges?import=true ${importCategory.length > 0 ? `&category=${encodeURIComponent(importCategory)}` : ''} `, - { - method: 'PUT', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify( - importImages?.map((image) => ({ - image: image.image, - post: image.link || '#', - category: importCategory - })) - ) - } - ).then(() => { - importMode = false; - importImages = undefined; - }); - - const migrateCategory = () => { - fetch( - `/api/badges?migrate=true&original=${encodeURIComponent( - (document.querySelector('#migrate_original') as HTMLInputElement).value - )}&new=${encodeURIComponent( - (document.querySelector('#migrate_new') as HTMLInputElement).value - )}`, - { - method: 'PUT' - } - ).then(() => (migrateMode = false)); - }; - - const hideCategory = () => { - hideCategoryQuery - .mutate({ - category: (document.querySelector('#category_hide') as HTMLInputElement).value - }) - .then(() => (hideMode = false)); - }; - - const removeHiddenBadges = (isOwner: boolean, badges: IndexedBadge[]) => - isOwner || authorised ? badges : badges.filter((b) => !b.hidden && !b.shadow_hidden); - - const setAdjacentCursor = (badges: IndexedBadge[], direction: number) => { - const currentCategory = selectedBadge?.category || 'Uncategorised'; - const currentBadge = selectedBadge?.index; - const categoryBadges = groupBadges(badges)[currentCategory]; - - if (!currentCategory || currentBadge === undefined) return; - - let previousBadge = categoryBadges[currentBadge + direction]; - - while (previousBadge && (previousBadge.hidden || previousBadge.shadow_hidden)) - previousBadge = categoryBadges[previousBadge.index + direction]; - - if (previousBadge) selectedBadge = previousBadge; - }; - - const adjacentBadgeExists = ( - selectedBadge: IndexedBadge | undefined, - badges: IndexedBadge[], - direction: number - ) => { - const currentCategory = selectedBadge?.category || 'Uncategorised'; - const currentBadge = selectedBadge?.index; - const categoryBadges = groupBadges(badges)[currentCategory]; - - if (!currentCategory || currentBadge === undefined || !categoryBadges) return; - - let previousBadge = categoryBadges[currentBadge + direction]; - - while (previousBadge && (previousBadge.hidden || previousBadge.shadow_hidden)) - previousBadge = categoryBadges[previousBadge.index + direction]; - - return previousBadge; - }; - - const castAsStringArray = (array: any[]) => array as string[]; - - const castBadgesToIndexedBadges = (array: any[]) => array as IndexedBadge[]; - - const shadowHideBadge = () => { - if (!selectedBadge && !authorised) return; - - shadowHideBadgeQuery - .mutate({ - id: badger.id as number, - state: selectedBadge?.shadow_hidden as boolean - }) - .then(); - }; + { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify( + importImages?.map((image) => ({ + image: image.image, + post: image.link || '#', + category: importCategory + })) + ) + } + ).then(() => { + importMode = false; + importImages = undefined; + }); + + const migrateCategory = () => { + fetch( + `/api/badges?migrate=true&original=${encodeURIComponent( + (document.querySelector('#migrate_original') as HTMLInputElement).value + )}&new=${encodeURIComponent( + (document.querySelector('#migrate_new') as HTMLInputElement).value + )}`, + { + method: 'PUT' + } + ).then(() => (migrateMode = false)); + }; + + const hideCategory = () => { + hideCategoryQuery + .mutate({ + category: (document.querySelector('#category_hide') as HTMLInputElement).value + }) + .then(() => (hideMode = false)); + }; + + const removeHiddenBadges = (isOwner: boolean, badges: IndexedBadge[]) => + isOwner || authorised ? badges : badges.filter((b) => !b.hidden && !b.shadow_hidden); + + const setAdjacentCursor = (badges: IndexedBadge[], direction: number) => { + const currentCategory = selectedBadge?.category || 'Uncategorised'; + const currentBadge = selectedBadge?.index; + const categoryBadges = groupBadges(badges)[currentCategory]; + + if (!currentCategory || currentBadge === undefined) return; + + let previousBadge = categoryBadges[currentBadge + direction]; + + while (previousBadge && (previousBadge.hidden || previousBadge.shadow_hidden)) + previousBadge = categoryBadges[previousBadge.index + direction]; + + if (previousBadge) selectedBadge = previousBadge; + }; + + const adjacentBadgeExists = ( + selectedBadge: IndexedBadge | undefined, + badges: IndexedBadge[], + direction: number + ) => { + const currentCategory = selectedBadge?.category || 'Uncategorised'; + const currentBadge = selectedBadge?.index; + const categoryBadges = groupBadges(badges)[currentCategory]; + + if (!currentCategory || currentBadge === undefined || !categoryBadges) return; + + let previousBadge = categoryBadges[currentBadge + direction]; + + while (previousBadge && (previousBadge.hidden || previousBadge.shadow_hidden)) + previousBadge = categoryBadges[previousBadge.index + direction]; + + return previousBadge; + }; + + const castAsStringArray = (array: any[]) => array as string[]; + + const castBadgesToIndexedBadges = (array: any[]) => array as IndexedBadge[]; + + const shadowHideBadge = () => { + if (!selectedBadge && !authorised) return; + + shadowHideBadgeQuery + .mutate({ + id: badger.id as number, + state: selectedBadge?.shadow_hidden as boolean + }) + .then(); + }; </script> <HeadTitle route={`${data.username}'s Badge Wall`} path={`/user/${data.username}`} /> {#if loadError} - <Popup fullscreen locked> - {loadError} - </Popup> + <Popup fullscreen locked> + {loadError} + </Popup> {:else} - {@const isOwner = $identity && (isId ? $identity.id : $identity.name) === data.username} - - {#if $BadgeWallUser.fetching || !$BadgeWallUser.data} - <Message message="Loading badges ..." /> - - <Skeleton grid={true} count={100} width="150px" height="170px" /> - {:else} - {@const ungroupedBadges = castBadgesToIndexedBadges($BadgeWallUser.data.User.badges)} - {@const isBadgeSelected = - selectedBadge && - selectedBadge !== undefined && - selectedBadge.image && - selectedBadge.image !== undefined && - !editMode} - - <div id="badges"> - {#if preferences && !preferences.hide_awc_badges} - <AWC {awcPromise} {categoryFilter} {isOwner} {preferences} /> - {/if} - - {#if ungroupedBadges === null} - <Message message="Loading badges ..." /> - - <Skeleton grid={true} count={10} width="150px" height="170px" /> - {:else} - {@const groupedBadges = Object.entries( - groupBadges(removeHiddenBadges(isOwner, ungroupedBadges)) - )} - - {#if isOwner || authorised} - {@const shadowHiddenCount = ungroupedBadges.filter((badge) => badge.shadow_hidden).length} - {@const shadowHidden = shadowHiddenCount > 0} - - {#if shadowHidden} - <div class="card"> - <b>Notice:</b> The Badge Wall overseer system has detected badges containing - AI-generated material on your wall. {shadowHiddenCount} of your badges have been shadow - hidden. - <p /> - You may use the "Un-shadow Hide Badges" button to unhide these badges, from where you will - be required to use the hide feature to hide these badges from the public, while allowing - them to stay visible to you as the account holder. - </div> - {:else if !noticeDismissed} - <div class="card"> - <b>Notice:</b> AniList has begun purging outbound links which contain AI-generated - material, this includes Badge Wall. If you have collected badges with AI-generated - elements, kindly use the hide feature to hide these badges from the public, while - allowing them to stay visible to you as the account holder. - <p /> - Failure to comply with this request at your earliest convenience will result in the hiding - of all badges from your Badge Wall. - <p /> - <button - on:click={() => { - noticeDismissed = true; - - localStorage.setItem('badgeWallNoticeDismissed', 'true'); - }} - > - Dismiss - </button> - </div> - {/if} - - <p /> - - <div class="card"> - {#if authorised} - <button on:click={setShadowHide}>Shadow Hide Badges</button> - {/if} - - {#if isOwner && authorised} - <span style="margin: 0 0.625rem;">•</span> - {/if} - - {#if isOwner} - <button - on:click={() => { - selectedBadge = undefined; - editMode = !editMode; - }} - > - {editMode - ? $locale().user.badges.editMode.disable - : $locale().user.badges.editMode.enable} - </button> - <span style="margin: 0 0.625rem;">•</span> - <button - on:click={() => { - selectedBadge = undefined; - importMode = !importMode; - }} - > - {importMode - ? $locale().user.badges.importMode.disable - : $locale().user.badges.importMode.enable} - </button> - <span style="margin: 0 0.625rem;">•</span> - <button - on:click={() => { - selectedBadge = undefined; - migrateMode = !migrateMode; - }} - > - Migrate Category - </button> - <span style="margin: 0 0.625rem;">•</span> - <button - on:click={() => { - selectedBadge = undefined; - hideMode = !hideMode; - }} - > - Hide Category - </button> - <!-- <!-- <span style="margin: 0 0.625rem;">•</span> --> - <!-- <button on:click={() => exportBadges(groupedBadges)}>Export Badges</button> --> - - {#if shadowHidden} - <span style="margin: 0 0.625rem;">•</span> - <button on:click={setShadowHide}>Un-shadow Hide Badges</button> - {/if} - - {#if editMode && isOwner} - {@const groups = groupedBadges - .map((group) => group[0]) - .filter((group) => group !== 'Uncategorised')} - {@const designers = castAsStringArray([ - ...new Set( - ungroupedBadges - .map((badge) => badge.designer) - .filter((designer) => designer !== undefined && designer !== null) - .filter( - (designer, index, array) => - array.indexOf(designer) === index && !array.includes(`@${designer}`) - ) - ) - ])} - - <p /> - - {#if error} - <p style="color: red;">{error}</p> - {/if} - - <input - type="text" - placeholder={$locale().user.badges.editMode.imageURL} - name="image_url" - minlength="1" - maxlength="1000" - size="15" - value={selectedBadge ? selectedBadge.image : ''} - /> - <input - type="text" - placeholder={$locale().user.badges.editMode.activityURL} - name="activity_url" - minlength="1" - maxlength="1000" - size="15" - value={selectedBadge - ? selectedBadge.post === '#' - ? '' - : selectedBadge.post - : ''} - /> - <input - type="text" - placeholder={$locale().user.badges.editMode.description} - name="description" - minlength="1" - maxlength="1000" - size="15" - value={selectedBadge ? selectedBadge.description : ''} - /> - <Dropdown - items={groups.map((group) => ({ - name: group, - url: '#', - onClick: () => { - const category = document.querySelector('input[name="category"]'); - - if (category instanceof HTMLInputElement) category.value = group; - } - }))} - header={false} - center={false} - > - <span slot="title"> - <input - type="text" - placeholder={$locale().user.badges.editMode.category} - name="category" - minlength="1" - maxlength="1000" - size="15" - value={selectedBadge - ? selectedBadge.category === 'Uncategorised' - ? '' - : selectedBadge.category - : ''} - list="categories" - /> - </span> - </Dropdown> - <span style="float: right;"> - <input - type="datetime-local" - value={selectedBadge && selectedBadge.time - ? dateToInputTime(databaseTimeToDate(selectedBadge.time)) - : ''} - /> - <small>Must be full date and time, defaults to now if any fields empty</small> - </span> - - <p /> - - <div class="edit-row-2"> - <input - type="text" - placeholder={$locale().user.badges.editMode.source} - name="source" - minlength="1" - maxlength="1000" - size="16" - value={selectedBadge ? selectedBadge.source : ''} - /> - <Dropdown - items={designers.map((designer) => ({ - name: designer, - url: '#', - onClick: () => { - const designerField = document.querySelector('input[name="designer"]'); - - if (designerField instanceof HTMLInputElement) - designerField.value = designer; - } - }))} - header={false} - center={false} - > - <span slot="title"> - <input - type="text" - placeholder={$locale().user.badges.editMode.designer} - name="designer" - minlength="1" - maxlength="1000" - size="17" - value={selectedBadge ? selectedBadge.designer : ''} - /> - </span> - </Dropdown> - <Dropdown - items={[false, true].map((hidden) => ({ - name: hidden ? 'Hidden' : 'Shown', - url: '#', - onClick: () => { - const hiddenInput = document.querySelector('input[name="hidden"]'); - - if (hiddenInput instanceof HTMLInputElement) - hiddenInput.value = hidden ? 'Hidden' : 'Shown'; - } - }))} - header={false} - center={false} - > - <span slot="title"> - <input - type="text" - placeholder="Shown" - name="hidden" - minlength="1" - maxlength="1000" - size="15" - value={selectedBadge - ? selectedBadge.hidden - ? 'Hidden' - : 'Shown' - : 'Shown'} - /> - </span> - </Dropdown> - <button class="button-lined" on:click={submitBadge} - >{selectedBadge - ? $locale().user.badges.editMode.update - : $locale().user.badges.editMode.add}</button - > - {#if selectedBadge} - {$locale().user.badges.editMode.or} - <button - class="button-lined" - on:click={() => { - if (selectedBadge) removeBadge(selectedBadge); - }}>{$locale().user.badges.editMode.delete}</button - > - {/if} - </div> - {/if} - {/if} - </div> - {/if} - - <p /> - - <Badges - {ungroupedBadges} - {groupedBadges} - {categoryFilter} - {editMode} - {preferences} - bind:selectedBadge - /> - {/if} - </div> - - {#if isBadgeSelected} - <!-- {@const anyAdjacentBadgeExists = + {@const isOwner = $identity && (isId ? $identity.id : $identity.name) === data.username} + + {#if $BadgeWallUser.fetching || !$BadgeWallUser.data} + <Message message="Loading badges ..." /> + + <Skeleton grid={true} count={100} width="150px" height="170px" /> + {:else} + {@const ungroupedBadges = castBadgesToIndexedBadges($BadgeWallUser.data.User.badges)} + {@const isBadgeSelected = + selectedBadge && + selectedBadge !== undefined && + selectedBadge.image && + selectedBadge.image !== undefined && + !editMode} + + <div id="badges"> + {#if preferences && !preferences.hide_awc_badges} + <AWC {awcPromise} {categoryFilter} {isOwner} {preferences} /> + {/if} + + {#if ungroupedBadges === null} + <Message message="Loading badges ..." /> + + <Skeleton grid={true} count={10} width="150px" height="170px" /> + {:else} + {@const groupedBadges = Object.entries( + groupBadges(removeHiddenBadges(isOwner, ungroupedBadges)) + )} + + {#if isOwner || authorised} + {@const shadowHiddenCount = ungroupedBadges.filter((badge) => badge.shadow_hidden).length} + {@const shadowHidden = shadowHiddenCount > 0} + + {#if shadowHidden} + <div class="card"> + <b>Notice:</b> The Badge Wall overseer system has detected badges containing + AI-generated material on your wall. {shadowHiddenCount} of your badges have been shadow + hidden. + <p /> + You may use the "Un-shadow Hide Badges" button to unhide these badges, from where you will + be required to use the hide feature to hide these badges from the public, while allowing + them to stay visible to you as the account holder. + </div> + {:else if !noticeDismissed} + <div class="card"> + <b>Notice:</b> AniList has begun purging outbound links which contain AI-generated + material, this includes Badge Wall. If you have collected badges with AI-generated + elements, kindly use the hide feature to hide these badges from the public, while + allowing them to stay visible to you as the account holder. + <p /> + Failure to comply with this request at your earliest convenience will result in the hiding + of all badges from your Badge Wall. + <p /> + <button + on:click={() => { + noticeDismissed = true; + + localStorage.setItem('badgeWallNoticeDismissed', 'true'); + }} + > + Dismiss + </button> + </div> + {/if} + + <p /> + + <div class="card"> + {#if authorised} + <button on:click={setShadowHide}>Shadow Hide Badges</button> + {/if} + + {#if isOwner && authorised} + <span style="margin: 0 0.625rem;">•</span> + {/if} + + {#if isOwner} + <button + on:click={() => { + selectedBadge = undefined; + editMode = !editMode; + }} + > + {editMode + ? $locale().user.badges.editMode.disable + : $locale().user.badges.editMode.enable} + </button> + <span style="margin: 0 0.625rem;">•</span> + <button + on:click={() => { + selectedBadge = undefined; + importMode = !importMode; + }} + > + {importMode + ? $locale().user.badges.importMode.disable + : $locale().user.badges.importMode.enable} + </button> + <span style="margin: 0 0.625rem;">•</span> + <button + on:click={() => { + selectedBadge = undefined; + migrateMode = !migrateMode; + }} + > + Migrate Category + </button> + <span style="margin: 0 0.625rem;">•</span> + <button + on:click={() => { + selectedBadge = undefined; + hideMode = !hideMode; + }} + > + Hide Category + </button> + <!-- <!-- <span style="margin: 0 0.625rem;">•</span> --> + <!-- <button on:click={() => exportBadges(groupedBadges)}>Export Badges</button> --> + + {#if shadowHidden} + <span style="margin: 0 0.625rem;">•</span> + <button on:click={setShadowHide}>Un-shadow Hide Badges</button> + {/if} + + {#if editMode && isOwner} + {@const groups = groupedBadges + .map((group) => group[0]) + .filter((group) => group !== 'Uncategorised')} + {@const designers = castAsStringArray([ + ...new Set( + ungroupedBadges + .map((badge) => badge.designer) + .filter((designer) => designer !== undefined && designer !== null) + .filter( + (designer, index, array) => + array.indexOf(designer) === index && !array.includes(`@${designer}`) + ) + ) + ])} + + <p /> + + {#if error} + <p style="color: red;">{error}</p> + {/if} + + <input + type="text" + placeholder={$locale().user.badges.editMode.imageURL} + name="image_url" + minlength="1" + maxlength="1000" + size="15" + value={selectedBadge ? selectedBadge.image : ''} + /> + <input + type="text" + placeholder={$locale().user.badges.editMode.activityURL} + name="activity_url" + minlength="1" + maxlength="1000" + size="15" + value={selectedBadge + ? selectedBadge.post === '#' + ? '' + : selectedBadge.post + : ''} + /> + <input + type="text" + placeholder={$locale().user.badges.editMode.description} + name="description" + minlength="1" + maxlength="1000" + size="15" + value={selectedBadge ? selectedBadge.description : ''} + /> + <Dropdown + items={groups.map((group) => ({ + name: group, + url: '#', + onClick: () => { + const category = document.querySelector('input[name="category"]'); + + if (category instanceof HTMLInputElement) category.value = group; + } + }))} + header={false} + center={false} + > + <span slot="title"> + <input + type="text" + placeholder={$locale().user.badges.editMode.category} + name="category" + minlength="1" + maxlength="1000" + size="15" + value={selectedBadge + ? selectedBadge.category === 'Uncategorised' + ? '' + : selectedBadge.category + : ''} + list="categories" + /> + </span> + </Dropdown> + <span style="float: right;"> + <input + type="datetime-local" + value={selectedBadge && selectedBadge.time + ? dateToInputTime(databaseTimeToDate(selectedBadge.time)) + : ''} + /> + <small>Must be full date and time, defaults to now if any fields empty</small> + </span> + + <p /> + + <div class="edit-row-2"> + <input + type="text" + placeholder={$locale().user.badges.editMode.source} + name="source" + minlength="1" + maxlength="1000" + size="16" + value={selectedBadge ? selectedBadge.source : ''} + /> + <Dropdown + items={designers.map((designer) => ({ + name: designer, + url: '#', + onClick: () => { + const designerField = document.querySelector('input[name="designer"]'); + + if (designerField instanceof HTMLInputElement) + designerField.value = designer; + } + }))} + header={false} + center={false} + > + <span slot="title"> + <input + type="text" + placeholder={$locale().user.badges.editMode.designer} + name="designer" + minlength="1" + maxlength="1000" + size="17" + value={selectedBadge ? selectedBadge.designer : ''} + /> + </span> + </Dropdown> + <Dropdown + items={[false, true].map((hidden) => ({ + name: hidden ? 'Hidden' : 'Shown', + url: '#', + onClick: () => { + const hiddenInput = document.querySelector('input[name="hidden"]'); + + if (hiddenInput instanceof HTMLInputElement) + hiddenInput.value = hidden ? 'Hidden' : 'Shown'; + } + }))} + header={false} + center={false} + > + <span slot="title"> + <input + type="text" + placeholder="Shown" + name="hidden" + minlength="1" + maxlength="1000" + size="15" + value={selectedBadge + ? selectedBadge.hidden + ? 'Hidden' + : 'Shown' + : 'Shown'} + /> + </span> + </Dropdown> + <button class="button-lined" on:click={submitBadge} + >{selectedBadge + ? $locale().user.badges.editMode.update + : $locale().user.badges.editMode.add}</button + > + {#if selectedBadge} + {$locale().user.badges.editMode.or} + <button + class="button-lined" + on:click={() => { + if (selectedBadge) removeBadge(selectedBadge); + }}>{$locale().user.badges.editMode.delete}</button + > + {/if} + </div> + {/if} + {/if} + </div> + {/if} + + <p /> + + <Badges + {ungroupedBadges} + {groupedBadges} + {categoryFilter} + {editMode} + {preferences} + bind:selectedBadge + /> + {/if} + </div> + + {#if isBadgeSelected} + <!-- {@const anyAdjacentBadgeExists = adjacentBadgeExists(selectedBadge, ungroupedBadges, -1) || adjacentBadgeExists(selectedBadge, ungroupedBadges, 1)} --> - <Popup - fullscreen - show={isBadgeSelected} - onLeave={() => { - selectedBadge = undefined; - }} - > - <BadgePreview - bind:selectedBadge - onNext={() => setAdjacentCursor(ungroupedBadges, 1)} - onPrevious={() => setAdjacentCursor(ungroupedBadges, -1)} - hasNext={adjacentBadgeExists(selectedBadge, ungroupedBadges, 1) !== undefined} - hasPrevious={adjacentBadgeExists(selectedBadge, ungroupedBadges, -1) !== undefined} - /> - - {#if authorised} - <button on:click={shadowHideBadge}> - {#if selectedBadge && selectedBadge.shadow_hidden} - Un-shadow - {:else} - Shadow - {/if} Hide Badge ({selectedBadge ? selectedBadge.id : 0}) - </button> - {/if} - </Popup> - {/if} - {/if} + <Popup + fullscreen + show={isBadgeSelected} + onLeave={() => { + selectedBadge = undefined; + }} + > + <BadgePreview + bind:selectedBadge + onNext={() => setAdjacentCursor(ungroupedBadges, 1)} + onPrevious={() => setAdjacentCursor(ungroupedBadges, -1)} + hasNext={adjacentBadgeExists(selectedBadge, ungroupedBadges, 1) !== undefined} + hasPrevious={adjacentBadgeExists(selectedBadge, ungroupedBadges, -1) !== undefined} + /> + + {#if authorised} + <button on:click={shadowHideBadge}> + {#if selectedBadge && selectedBadge.shadow_hidden} + Un-shadow + {:else} + Shadow + {/if} Hide Badge ({selectedBadge ? selectedBadge.id : 0}) + </button> + {/if} + </Popup> + {/if} + {/if} {/if} {#if true} - <Popup fullscreen onLeave={() => (importMode = false)} show={importMode}> - {$locale().user.badges.importMode.title} - - <p /> - - <input - type="text" - placeholder={$locale().user.badges.editMode.activityURL} - id="import_activity_url" - minlength="1" - maxlength="1000" - size="20" - /> - <input - type="text" - placeholder={$locale().user.badges.editMode.category} - id="import_category" - minlength="1" - maxlength="1000" - size="20" - /> - - <p /> - - <input type="checkbox" id="import_links" name="import_links" bind:checked={importLinks} /> - {$locale().user.badges.importMode.importLinks.title} - <SettingHint lineBreak> - {$locale().user.badges.importMode.importLinks.hint} - </SettingHint> - - <p /> - - <input type="checkbox" id="import_links" name="import_links" bind:checked={importReplies} /> - {$locale().user.badges.importMode.importReplies} - - <p /> - - <button - on:click={() => { - importMode = false; - importImages = undefined; - }} - class="button-lined" - > - {$locale().user.badges.importMode.cancel} - </button> - <button on:click={() => parsePost()} class="button-lined" style="float: right;"> - {$locale().user.badges.importMode.fetch} - </button> - - <p /> - - <details> - <summary>{$locale().user.badges.importMode.dangerous}</summary> - - <button - class="button-lined no-shadow" - data-umami-event="Remove All Badges" - on:click={removeAllBadges} - > - {$locale({ - values: { - times: 3 - confirmPrune - } - }).user.badges.importMode.deleteAll.title} - </button> - <SettingHint lineBreak> - {$locale().user.badges.importMode.deleteAll.hint} - </SettingHint> - </details> - - {#if importImages && importImages.length > 0} - <p /> - - {$locale({ - values: { - count: importImages.length - } - }).user.badges.importMode.importConfirm} - <button - on:click={() => importBadges()} - class="button-lined no-shadow" - data-umami-event="Import Badges" - > - {$locale().user.badges.importMode.import} - </button> - - <SettingHint lineBreak> - {$locale().user.badges.importMode.importWait} - </SettingHint> - {/if} - </Popup> + <Popup fullscreen onLeave={() => (importMode = false)} show={importMode}> + {$locale().user.badges.importMode.title} + + <p /> + + <input + type="text" + placeholder={$locale().user.badges.editMode.activityURL} + id="import_activity_url" + minlength="1" + maxlength="1000" + size="20" + /> + <input + type="text" + placeholder={$locale().user.badges.editMode.category} + id="import_category" + minlength="1" + maxlength="1000" + size="20" + /> + + <p /> + + <input type="checkbox" id="import_links" name="import_links" bind:checked={importLinks} /> + {$locale().user.badges.importMode.importLinks.title} + <SettingHint lineBreak> + {$locale().user.badges.importMode.importLinks.hint} + </SettingHint> + + <p /> + + <input type="checkbox" id="import_links" name="import_links" bind:checked={importReplies} /> + {$locale().user.badges.importMode.importReplies} + + <p /> + + <button + on:click={() => { + importMode = false; + importImages = undefined; + }} + class="button-lined" + > + {$locale().user.badges.importMode.cancel} + </button> + <button on:click={() => parsePost()} class="button-lined" style="float: right;"> + {$locale().user.badges.importMode.fetch} + </button> + + <p /> + + <details> + <summary>{$locale().user.badges.importMode.dangerous}</summary> + + <button + class="button-lined no-shadow" + data-umami-event="Remove All Badges" + on:click={removeAllBadges} + > + {$locale({ + values: { + times: 3 - confirmPrune + } + }).user.badges.importMode.deleteAll.title} + </button> + <SettingHint lineBreak> + {$locale().user.badges.importMode.deleteAll.hint} + </SettingHint> + </details> + + {#if importImages && importImages.length > 0} + <p /> + + {$locale({ + values: { + count: importImages.length + } + }).user.badges.importMode.importConfirm} + <button + on:click={() => importBadges()} + class="button-lined no-shadow" + data-umami-event="Import Badges" + > + {$locale().user.badges.importMode.import} + </button> + + <SettingHint lineBreak> + {$locale().user.badges.importMode.importWait} + </SettingHint> + {/if} + </Popup> {/if} <Popup fullscreen onLeave={() => (migrateMode = false)} show={migrateMode}> - Migrate Category - - <p /> - - <input - type="text" - placeholder="Original Category" - id="migrate_original" - minlength="1" - maxlength="1000" - size="20" - /> - <input - type="text" - placeholder="New Category" - id="migrate_new" - minlength="1" - maxlength="1000" - size="20" - /> - <SettingHint lineBreak>Leave category empty to migrate all to or from uncategorised.</SettingHint> - - <p /> - - <button - on:click={() => { - importMode = false; - importImages = undefined; - }} - class="button-lined" - > - {$locale().user.badges.importMode.cancel} - </button> - <button on:click={() => migrateCategory()} class="button-lined" style="float: right;"> - Migrate - </button> + Migrate Category + + <p /> + + <input + type="text" + placeholder="Original Category" + id="migrate_original" + minlength="1" + maxlength="1000" + size="20" + /> + <input + type="text" + placeholder="New Category" + id="migrate_new" + minlength="1" + maxlength="1000" + size="20" + /> + <SettingHint lineBreak>Leave category empty to migrate all to or from uncategorised.</SettingHint> + + <p /> + + <button + on:click={() => { + importMode = false; + importImages = undefined; + }} + class="button-lined" + > + {$locale().user.badges.importMode.cancel} + </button> + <button on:click={() => migrateCategory()} class="button-lined" style="float: right;"> + Migrate + </button> </Popup> <Popup fullscreen onLeave={() => (hideMode = false)} show={hideMode}> - Hide Category - - <SettingHint lineBreak> - If the majority of the badges in a category are shown, the category will be hidden, and vice - versa. - </SettingHint> - - <p /> - - <input - type="text" - placeholder="Category" - id="category_hide" - minlength="1" - maxlength="1000" - size="20" - /> - <SettingHint lineBreak>Leave category field empty to hide all.</SettingHint> - - <p /> - - <button - on:click={() => { - hideMode = false; - importImages = undefined; - }} - class="button-lined" - > - {$locale().user.badges.importMode.cancel} - </button> - <button on:click={() => hideCategory()} class="button-lined" style="float: right;" - >Toggle Visibility</button - > + Hide Category + + <SettingHint lineBreak> + If the majority of the badges in a category are shown, the category will be hidden, and vice + versa. + </SettingHint> + + <p /> + + <input + type="text" + placeholder="Category" + id="category_hide" + minlength="1" + maxlength="1000" + size="20" + /> + <SettingHint lineBreak>Leave category field empty to hide all.</SettingHint> + + <p /> + + <button + on:click={() => { + hideMode = false; + importImages = undefined; + }} + class="button-lined" + > + {$locale().user.badges.importMode.cancel} + </button> + <button on:click={() => hideCategory()} class="button-lined" style="float: right;" + >Toggle Visibility</button + > </Popup> |